package com.hydra.framework.event.fw;


import android.util.Log;

import com.hydra.framework.event.core.EventAction;
import com.hydra.framework.event.core.EventDispatcher;
import com.hydra.framework.event.core.EventIntent;
import com.hydra.framework.event.core.EventReceiver;
import com.hydra.framework.event.core.EventSender;
import com.hydra.framework.event.core.helper.DefaultEventThreadWrapper;
import com.hydra.framework.thread.ThreadBus;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static com.hydra.framework.event.core.helper.EventHelper.getExcludeSystemMethods;

/**
 * Created by Hydra.
 * 一个典型的EventDispatcher的自定义中心
 */
public class FWEvent {

    public static final String FWEVENT_LOG_TAG = "FWEvent";

    private static final EventDispatcher sFWEventDispatcher;

    static {
        sFWEventDispatcher = new EventDispatcher();
    }

    private static class FWMethodNode {
        public Method method;
        public FWEventAnnotation methodAnnotation;
    }

    private static HashMap<Class<?>, ArrayList<FWMethodNode>> receiverClassMethodsCache
            = new HashMap<>();

    private static final Object clazzToFWEventMethodsLock = new byte[0];

    //这里面包含dst的父类中的method
    private static ArrayList<FWMethodNode> getFWEventMethods(Object dst) {
        ArrayList<FWMethodNode> methodList;

        synchronized (clazzToFWEventMethodsLock) {
            methodList = receiverClassMethodsCache.get(dst.getClass());

            if (methodList != null) {
                return methodList;
            } else {
                methodList = new ArrayList<>();
                receiverClassMethodsCache.put(dst.getClass(), methodList);
            }
        }

        List<Method> methods = getExcludeSystemMethods(dst.getClass());

        for (Method method : methods) {
            FWEventAnnotation annotation = method.getAnnotation(FWEventAnnotation.class);

            if (annotation != null) {
                FWMethodNode node = new FWMethodNode();
                node.method = method;
                node.methodAnnotation = annotation;

                methodList.add(node);
            }
        }

        return methodList;
    }

    //因为FWEvent的action都是固定的，所以可以cache起来，减少生成很多对象
    private static HashMap<FWEventActionKey, EventAction> fwEventActionCache = new HashMap<>();

    private static final Object fwEventActionsLock = new byte[0];

    public static EventAction fwEventAction(FWEventActionKey fwEventActionKey) {
        EventAction fwEventAction;

        synchronized (fwEventActionsLock) {
            fwEventAction = fwEventActionCache.get(fwEventActionKey);

            if(fwEventAction == null) {
                fwEventAction = new EventAction(fwEventActionKey, fwEventActionKey.sticky);

                fwEventActionCache.put(fwEventActionKey, fwEventAction);
            }
        }

        return fwEventAction;
    }

    public static void autoBindingEvent(Object target) {
        ArrayList<FWMethodNode> nodes = getFWEventMethods(target);
        for (FWMethodNode node : nodes) {
            sFWEventDispatcher.addBinding(fwEventAction(node.methodAnnotation.name()),
                    buildFWEventReceiver(node.methodAnnotation, target, node.method));
        }
    }

    public static void autoRemoveEvent(Object target) {
        ArrayList<FWMethodNode> nodes = getFWEventMethods(target);
        for (FWMethodNode node : nodes) {
            sFWEventDispatcher.removeBinding(fwEventAction(node.methodAnnotation.name()),
                    buildFWEventReceiver(node.methodAnnotation, target, node.method));
        }
    }

    /**
     * 绑定单个函数
     */
    public static void bindMethodToEvent(Object target, String methodName) {
        try {
            Method method = target.getClass().getMethod(methodName, EventIntent.class);

            if(method != null) {
                bindEventTo(target, method);
            }
        } catch (NoSuchMethodException e) {
            Log.e(FWEVENT_LOG_TAG, "bind event failed no method : " + methodName
                    + " in " + target.toString());
        }
    }

    /**
     * 绑定单个函数
     */
    public static void bindEventTo(Object target, Method method) {
        FWEventAnnotation annotation = method.getAnnotation(FWEventAnnotation.class);

        if (annotation != null) {
            sFWEventDispatcher.addBinding(fwEventAction(annotation.name()),
                    buildFWEventReceiver(annotation, target, method));
        }
    }

    /**
     * 解绑单个函数
     */
    public static void removeMethodFromEvent(Object target, String methodName) {
        try {
            Method method = target.getClass().getMethod(methodName, EventIntent.class);

            if(method != null) {
                removeEventFrom(target, method);
            }
        } catch (NoSuchMethodException e) {
            Log.e(FWEVENT_LOG_TAG, "remove event failed no method : " + methodName
                    + " in " + target.toString());
        }
    }

    /**
     * 解绑单个函数
     */
    public static void removeEventFrom(Object target, Method method) {
        FWEventAnnotation annotation = method.getAnnotation(FWEventAnnotation.class);

        if (annotation != null) {
            sFWEventDispatcher.removeBinding(fwEventAction(annotation.name()),
                    buildFWEventReceiver(annotation, target, method));
        }
    }

    /**
     *
     * @param thread 参考thread bus中的线程id
     */
    public static void sendEventAsync(final Object senderObj, final FWEventActionKey fwEventActionKey, int thread, final Object...args) {
        ThreadBus.post(thread, new Runnable() {
            @Override
            public void run() {
                sendEvent(senderObj, fwEventActionKey, args);
            }
        });
    }

    public static void sendEvent(Object senderObj, FWEventActionKey fwEventActionKey, Object...args) {
        EventIntent eventIntent = buildFWEventIntent(senderObj, fwEventActionKey);

        eventIntent.addArgs(args);

        sFWEventDispatcher.notifyEvent(eventIntent);
    }

    private static EventReceiver buildFWEventReceiver(FWEventAnnotation annotation,
                                                      Object target, Method method) {
        return new EventReceiver(target, method, DefaultEventThreadWrapper.thread(annotation.thread()),
                annotation.priority(), annotation.flag());
    }

    private static EventIntent buildFWEventIntent(Object senderObj, FWEventActionKey fwEventActionKey) {
        EventSender eventSender = new EventSender(senderObj);
        EventAction fwEventAction = fwEventAction(fwEventActionKey);

        return new EventIntent(eventSender, fwEventAction, false);
    }
}
